﻿
using System;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine
{
    public delegate void ModuleConnectedChangedHandler(ModuleOutput output, ModuleInput input);

    /// <summary>
    /// Defines all types of module io.
    /// </summary>
    public enum IOType
    {
        Audio,
        Midi,
        Waveform,
        AudioOrWaveform
    }

    public abstract class ModuleIO
    {
        public const double INVALID = 0.0;
        public const double NOTE_ON = 1.0;
        public const double NOTE_OFF = 0.9;

        internal static bool DisableCallbacks { get; private set; }

        internal static void SetDisabledCallbacks(bool disabled)
        {
            if (disabled == DisableCallbacks)
            {
                throw new Exception("DisableCallbacks as already " + disabled);
            }
            DisableCallbacks = disabled;
        }

        private int mID;

        public virtual bool InternallyVisible { get; set; }

        public string ShortName { get; set; }

        public string Name { get; set; }

        public int ID
        {
            get { return mID; }
            internal set
            {
                mID = value;
            }
        }

        public virtual double VisualForce { get; }

        public BaseModule Module { get; }

        public abstract bool IsConnected { get; }

        public IOType Type { get; private set; }

        public IOType SwitchedType { get; internal set; }

        public IOType ActualType
        {
            get
            {
                if (Type == IOType.AudioOrWaveform)
                {
                    if (SwitchedType == BaseModule.NO_TYPE)
                    {
                        return IOType.AudioOrWaveform;
                    }
                    return SwitchedType;
                }

                return Type;
            }
        }

        /// <summary>
        /// Get if this connection is circular.
        /// <para>Then this module connection willbe processable anyway with a risk for a delay artifact.</para>
        /// </summary>
        public abstract bool IsCircularConnected { get; }

        public event ModuleConnectedChangedHandler OnConnected;

        public event ModuleConnectedChangedHandler OnDisconnected;

        protected void RaiseOnConnected(ModuleOutput output, ModuleInput input)
        {
            if (!DisableCallbacks)
            {
                OnConnected?.Invoke(output, input);
            }
        }

        protected void RaiseOnDisconnected(ModuleOutput output, ModuleInput input)
        {
            if (!DisableCallbacks)
            {
                OnDisconnected?.Invoke(output, input);
            }
        }

        public ModuleIO(string name, IOType type, BaseModule module)
            : this(name, name, type, module)
        {
        }
        
        public ModuleIO(string shortName, string name, IOType type, BaseModule module)
        {
            SwitchedType = BaseModule.NO_TYPE;
            ShortName = shortName;
            Name = name;
            Module = module;
            Type = type;
        }
        
        protected bool FindModuleViaOutputs(BaseModule module, BaseModule next)
        {
            foreach (var nextOutput in next.Outputs)
            {
                foreach (var connectedInput in nextOutput.ConnectedInputs)
                {
                    if (!connectedInput.IsConnected)
                    {
                        continue;
                    }

                    if (connectedInput.Module == module)
                    {
                        // Found module.
                        return true;
                    }

                    if (FindModuleViaOutputs(module, connectedInput.Module))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        protected bool FindModuleViaInputs(BaseModule module, BaseModule next)
        {
            foreach (var nextInput in next.Inputs)
            {
                if (!nextInput.IsConnected)
                {
                    continue;
                }

                if (nextInput.FromOutput.Module == module)
                {
                    // Found module.
                    return true;
                }

                if (FindModuleViaInputs(module, nextInput.FromOutput.Module))
                {
                    return true;
                }
            }

            return false;
        }

        internal virtual void ResetupSwitchable(IOType type)
        {
            if (Type == IOType.AudioOrWaveform)
            {
                if (!(type == IOType.Audio || type == IOType.Waveform || type == IOType.AudioOrWaveform))
                {
                    throw new System.Exception("Resetup switchable method is only compatible with IOType of Audio or Waveform");
                }

                SwitchedType = type;
            }
        }

        /// <summary>
        /// Check if type is compatible with this output.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public bool MatchType(IOType type)
        {
            if (IsDirectMatchType(type))
            {
                return true;
            }
            else if (Type != IOType.Midi && type != IOType.Midi)
            {
                return true;
            }

            return false;
        }

        public bool IsDirectMatchType(IOType type)
        {
            return Type == type;
        }

        /// <summary>
        /// Check if it is possible to connect to this output with a specific type.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public bool PossibleToConnect(ModuleIO moudleIO)
        {
            if (this is ModuleInput && moudleIO is ModuleInput)
            {
                return false;
            }
            else if (this is ModuleOutput && moudleIO is ModuleOutput)
            {
                return false;
            }
            
            // Check if it is a direct match.
            if (IsDirectMatchType(moudleIO.ActualType))
            {
                return true;
            }

            // If type is a match but must be switched.
            if (MatchType(moudleIO.ActualType))
            {
                // Check if this IO's module can switch type.
                if (Module.CanSwitchType(moudleIO.ActualType))
                {
                    return true;
                }

                // Check ifremote IO's module can switch type.
                if(moudleIO.Module.CanSwitchType(ActualType))
                {
                    return true;
                }
            }

            // If we comes here, there may be already connected input/output with another IOType.
            // Not possible to connect.
            return false;
        }

        public override string ToString()
        {
            return "ID: " + ID + " Module: " + Module.Name;
        }
    }
}
